package com.atguigu.gmall.cart.service.impl;


import com.atguigu.gmall.cart.service.CartInfoAsyncService;
import com.atguigu.gmall.cart.service.CartInfoService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.util.AuthContextHolder;
import com.atguigu.gmall.model.cart.CartInfo;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class CartInfoServiceImpl implements CartInfoService {


    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private CartInfoAsyncService cartInfoAsyncService;



    //加入购物车
    @Override
    public CartInfo addCart(Long skuId, Integer skuNum) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();


        //获取真实用户ID(虽然AuthContextHolder中方法区分了真实和临时的id，但是我们此处不必纠结前端传过来的是真实还是临时的，因为一但是真实的，就不用获取临时的了，因此先直接当做真实的来判断)
        String userId = AuthContextHolder.getUserId(request);
        if (StringUtils.isEmpty(userId)){//如果真实id不是空，这段代码根本不用执行，才会采用临时ID
            //临时用户ID
            userId = AuthContextHolder.getUserTempId(request);//不用再起一个别的对象名了，这里在本类仅仅作为参数传递一下，下面  的所有代码无论是真实还是临时，直接开开心心执行即可，因为真实或是临时执行的代码是一样的
        }


        //String userId = "3";

        //1、写Redis 同步
        // Hash类型  散列类型
        // 参数1： h   Map h = new HashMap();  用户ID   user:3:cart
        // 参数2： hk   h.put(k,               skuId
        // 参数3： hv   h.put(k,v)             CartInfo

        String cacheH = RedisConst.USER_KEY_PREFIX+userId+RedisConst.USER_CART_KEY_SUFFIX;
        String cacheHk = skuId.toString();
        //1.1 查询 Redis还是DB
        CartInfo cartInfo = (CartInfo) redisTemplate.opsForHash().get(cacheH, cacheHk);
        if (cartInfo != null){
            //缓存中已经存在了  追加数量
            cartInfo.setSkuNum(cartInfo.getSkuNum()+skuNum);
            //选中8
            cartInfo.setIsChecked(1);

            //2、Mysql  异步  保存购物车信息 （备份）
                cartInfoAsyncService.updateByIdAsync(cartInfo);//运行时会报错，因为缓存中是没有数据库的主键的


        }else {
            //直接作为新商品 添加  Long skuId, Integer skuNum
            cartInfo = new CartInfo();
            //商品ID
            cartInfo.setSkuId(skuId);
            //数量
            cartInfo.setSkuNum(skuNum);

            //库存信息对象
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            cartInfo.setUserId(userId);
            //购物车价格
            cartInfo.setCartPrice(skuInfo.getPrice());
            //图片
            cartInfo.setImgUrl(skuInfo.getSkuDefaultImg());
            //名称
            cartInfo.setSkuName(skuInfo.getSkuName());
            //实时价格(第一次添加和购物车中的价格应该是一样的)
            cartInfo.setSkuPrice(skuInfo.getPrice());


            //2、Mysql  异步  保存购物车信息 （备份）
            cartInfoAsyncService.insertAsync(cartInfo);//子线程

        }

        //保存到缓存中
        redisTemplate.opsForHash().put(cacheH,cacheHk,cartInfo);

        //设置过期时间  本次不设置了 算了
        return cartInfo;
    }


    //查询新添加的购物车商品
    @Override
    public CartInfo toCart(Long skuId) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();


        //获取真实用户ID(虽然AuthContextHolder中方法区分了真实和临时的id，但是我们此处不必纠结前端传过来的是真实还是临时的，因为一但是真实的，就不用获取临时的了，因此先直接当做真实的来判断)
        String userId = AuthContextHolder.getUserId(request);
        if (StringUtils.isEmpty(userId)){//如果真实id不是空，这段代码根本不用执行，才会采用临时ID
            //临时用户ID
            userId = AuthContextHolder.getUserTempId(request);//不用再起一个别的对象名了，这里在本类仅仅作为参数传递一下，下面  的所有代码无论是真实还是临时，直接开开心心执行即可，因为真实或是临时执行的代码是一样的
        }
        String cacheH = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
        String cacheHk = skuId.toString();
        return (CartInfo) redisTemplate.opsForHash().get(cacheH,cacheHk);
    }

    //查询当前用户的购物车集合  （当前用户 ）
    // 当前用户 ： 临时用户
    // 当前用户 ： 真实用户
    // 当前用户 ： 真实用户 + 临时用户
    @Override
    public List<CartInfo> cartList(String userId, String userTempId) {
        //判断
        if(!StringUtils.isEmpty(userId)){
            //有真实用户
            if(!StringUtils.isEmpty(userTempId)){
                //有临时用户  合并购物车集合之再返回
                return mergeCartList(userId,userTempId);
            }else{
                //无临时用户  返回真实用户的购物车集合
                return getCartList(userId);
            }
        }else{
            //无真实用户
            if(!StringUtils.isEmpty(userTempId)){
                //有临时用户  返回临时用户的购物车集合
                return getCartList(userTempId);
            }
        }
        //啥也没有
        return null;
    }

    //复选框
    @Override
    public void checkCart(Long skuId, Integer isChecked) {
        //真实用户或临时用户ID
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //获取真实用户ID
        String userId = AuthContextHolder.getUserId(request);
        //同步更新缓存
        String cacheH = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
        String cacheHk = skuId.toString();
        CartInfo cartInfo = (CartInfo) redisTemplate.opsForHash().get(cacheH, cacheHk);//取出每一个购物车
        cartInfo.setIsChecked(isChecked);//然后重新设置选中字段
        redisTemplate.opsForHash().put(cacheH,cacheHk,cartInfo);//最后再放进去
        //异步更新DB
        cartInfoAsyncService.updateByIdAsync(cartInfo);
    }


    //商品清单
    @Override
    public List<CartInfo> getCartListChecked(String userId) {
        String cacheH = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
        List<CartInfo> cartInfoList = redisTemplate.opsForHash().values(cacheH);
        //实时价格  如果加了Canal就没有必须写实时价格
        return cartInfoList.stream().filter(cartInfo -> cartInfo.getIsChecked() == 1).collect(Collectors.toList());
    }


    //合并购物车集合
    private List<CartInfo> mergeCartList(String userId, String userTempId) {
        //1:获取真实用户购物车集合
        List<CartInfo> cartListUserId = getCartList(userId);
        //2:获取临时用户购物车集合
        List<CartInfo> cartListUserTempId = getCartList(userTempId);
        //3:判断
        if(!CollectionUtils.isEmpty(cartListUserId)){
            //有：真实用户的购物车集合
            if(!CollectionUtils.isEmpty(cartListUserTempId)){
                //有：临时用户的购物车集合   合并真+临用户的购物车集合  超难
                // Map  K skuId  V:CartInfo
                Map<Long, CartInfo> cartInfoMapUserId = cartListUserId.stream().collect(Collectors.toMap(CartInfo::getSkuId, cartInfo -> cartInfo));

                cartListUserTempId.stream().forEach(cartInfoUserTempId -> {
                    //1、判断 临时购物车 是否在真实购物车集合中已经存在
                    CartInfo cartInfoUserId = cartInfoMapUserId.get(cartInfoUserTempId.getSkuId());
                    if(null != cartInfoUserId){
                        // 1.1 存在相同的  追加数量 设置选中
                        cartInfoUserId.setSkuNum(cartInfoUserId.getSkuNum() + cartInfoUserTempId.getSkuNum());
                        cartInfoUserId.setIsChecked(1);
                        //更新Redis
                        String cacheHUserId = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
                        redisTemplate.opsForHash().put(cacheHUserId, cartInfoUserId.getSkuId().toString(),cartInfoUserId);
                        // 异步更新DB
                        cartInfoAsyncService.updateByIdAsync(cartInfoUserId);
                    }else{
                        // 1.2 不存在相同的 添加新购物车
                        //注意此时MAP的key值是对象，相当于一个容器，不用重新提交map，就value值就已经改变了
                        cartInfoMapUserId.put(cartInfoUserTempId.getSkuId(),cartInfoUserTempId);
                        //添加Redis
                        cartInfoUserTempId.setUserId(userId);
                        String cacheHUserId = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
                        redisTemplate.opsForHash().put(cacheHUserId, cartInfoUserTempId.getSkuId().toString(),cartInfoUserTempId);
                        //异步添加DB
                        cartInfoAsyncService.insertAsync(cartInfoUserTempId);
                    }
                });
                //DB中统一删除临时用户的购物车集合
                cartInfoAsyncService.deleteByUserTempId(userTempId);

                //整体将临时用户的购物车 在redis 删除掉
                String cacheHUserTempId = RedisConst.USER_KEY_PREFIX + userTempId +
                        RedisConst.USER_CART_KEY_SUFFIX;
                //将临时用户的缓存删除掉
                redisTemplate.delete(cacheHUserTempId);
                // cartInfoMapUserId  将Map转回List集合
                return new ArrayList(cartInfoMapUserId.values());
            }else{
                //无：临时用户的购物车集合
                return cartListUserId;
            }
        }else{
            //无：真实用户的购物车集合
            if(!CollectionUtils.isEmpty(cartListUserTempId)){
                //有：临时用户的购物车集合  直接返回临时用户购物车集合
                //因为真实用户已经存在了 已经登录了 更新缓存（主线程）   及 更新DB（子线程）（异步）
                //HashMap   Map map =
                //          map.remove(k)
                String cacheHUserTempId = RedisConst.USER_KEY_PREFIX + userTempId + RedisConst.USER_CART_KEY_SUFFIX;
                String cacheHUserId = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
                //String cacheH = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
                //List<CartInfo> cartListUserTempId
                Map<String, CartInfo> cartInfoMap = cartListUserTempId.stream().map(cartInfo -> {
                    //临时用户ID改真实用户ID
                    cartInfo.setUserId(userId);//购物车表中的每一条数据
                    return cartInfo;
                }).collect(Collectors.toMap(CartInfo->CartInfo.getSkuId().toString(), cartInfo -> cartInfo));
                //添加真实用户的缓存
                redisTemplate.opsForHash().putAll(cacheHUserId,cartInfoMap);
                //将临时用户的缓存删除掉
                redisTemplate.delete(cacheHUserTempId);
                //开启子线程 更新DB
                cartInfoAsyncService.updateByIdAsyncExt(userTempId,userId);
                return cartListUserTempId;
            }
        }
        //啥也没有
        return null;
    }




    //根据用户ID获取购物车集合
    private List<CartInfo> getCartList(String userId) {
        String cacheH = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
        //有可能更新一下实时价格
        List<CartInfo> cartInfoList = redisTemplate.opsForHash().values(cacheH);
        cartInfoList.forEach(cartInfo -> {
            cartInfo.setSkuPrice(productFeignClient.getPrice(cartInfo.getSkuId()));
        });
        //排序  课上按照ID倒序      作业：课下同学要给购物车加时间字段 按照时间排序
        return cartInfoList.stream().sorted((o1,o2)->{
            return o2.getSkuNum()-o1.getSkuNum();
        }).collect(Collectors.toList());

    }


    //去购物车结算 再次查询实时价格   页面回显的时候  价格就正确了



}
